package producersConsumersTests.bypassersCount;

import java.util.LinkedList;
import java.util.List;

import prod_cons.TaskContainer;
import producersConsumersTests.results.EventSupervisor;
import producersConsumersTests.utils.Inserter;
import producersConsumersTests.utils.Remover;
import producersConsumersTests.utils.TaskFactory;
import producersConsumersTests.utils.Test;
import producersConsumersTests.utils.Tester;
import producersConsumersTests.utils.TesterVisitor;

/**
 * Test #5 - how many tasks have bypassed a task. This test launches the
 * Producer consumer test with TimeSettingInserters and TimeSettingRemovers.
 * TimeSettingRemovers save the extracted tasks to a list, for later
 * investigation.
 * 
 * After the run has ended, the test adds the tasks to a timeline to see how
 * many tasks bypassed other tasks.
 * 
 * TEST OVERHEAD - list insertion after each task removal, and time setting on
 * insertion and removal
 * 
 */
public class BypassCountTest implements Test {
	private static class ClockVectorTaskFactory implements
			TaskFactory<ClockVectorTask> {
		private final int insertersNum, removersNum, id;

		public ClockVectorTaskFactory(int id, int insertersNum, int removersNum) {
			this.insertersNum = insertersNum;
			this.removersNum = removersNum;
			this.id = id;
		}

		@Override
		public ClockVectorTask createTask(long taskId) {
			ClockVectorTask t = new ClockVectorTask(id, insertersNum,
					removersNum);
			return t;
		}
	}

	private static class NumberTakingInserter extends Inserter<ClockVectorTask> {
		private int insertionNum = 0;
		private final int updateParam;
		private final int id;

		public NumberTakingInserter(int id, TaskContainer container,
				double lambda, TaskFactory<ClockVectorTask> factory,
				int updateParam) {
			super(container, lambda, factory);
			this.id = id;
			this.updateParam = updateParam;
		}

		public NumberTakingInserter(int id, TaskContainer container,
				int peakLength, int timeBetweenPeaks,
				TaskFactory<ClockVectorTask> factory, int updateParam) {
			super(container, peakLength, timeBetweenPeaks, factory);
			this.id = id;
			this.updateParam = updateParam;
		}

		@Override
		public void beforeInsert(ClockVectorTask t) {
			t.updateEntry(id);
		}

		@Override
		public void afterInsert(ClockVectorTask t) {
			Schedule.get().incrementEntranceEntry(id, id);
			if (++insertionNum == updateParam) {
				Schedule.get().updateEntryRow(id, id);
				insertionNum = 0;
			}
		}
	}

	private static class NumberTakingRemover extends Remover {
		private int removalNum = 0;
		private final int updateParam;
		private final int id;
		private List<ClockVectorTask> measures = new LinkedList<ClockVectorTask>();
		private long[] exitVectorClock;

		public NumberTakingRemover(int id, TaskContainer container,
				int updateParam, int removersNum) {
			super(container);
			this.id = id;
			this.updateParam = updateParam;
			exitVectorClock = new long[removersNum];
		}

		@Override
		protected void beforeGetAttempt() {
			for (int i = 0; i < exitVectorClock.length; i++)
				exitVectorClock[i] = Schedule.get().getExit(i, id);
		}

		@Override
		protected void afterGetAttempt(Runnable task) {
			ClockVectorTask timed = (ClockVectorTask) task;
			if (task != null) {
				Schedule.get().incrementExitEntry(id, id);
				if (++removalNum == updateParam) {
					Schedule.get().updateExitRow(id, id);
					removalNum = 0;
				}
				measures.add(timed);
				timed.setExit(exitVectorClock);
				timed.setRemoverId(id);
			}
		}

		public List<ClockVectorTask> getMeasures() {
			return measures;
		}
	}

	private static class BypassCountTester extends
			Tester<NumberTakingInserter, NumberTakingRemover> {
		private List<ClockVectorTask> tasks = new LinkedList<ClockVectorTask>();

		public BypassCountTester(NumberTakingInserter[] inserters,
				NumberTakingRemover[] removers, int timeToRun,
				TaskContainer container) {
			super(inserters, removers, timeToRun, container);
		}

		private EventSupervisor eventSupervisor;
		private double average;

		@Override
		protected void afterRun() {
			// get all tasks and add them to the list
			for (NumberTakingRemover remover : removers) {
				tasks.addAll(remover.getMeasures());
			}
			System.out.println("Test complete!");
			eventSupervisor = new EventSupervisor(tasks);
			long time = System.currentTimeMillis();
			try {
				average = eventSupervisor.calculateAverageBypassers();
				System.out.println(average + " bypassed, analysis cost: "
						+ (System.currentTimeMillis() - time) + "ms");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}

		@Override
		protected void beforeRun() {
			Schedule.init(inserters.length, removers.length);
		}

		public List<ClockVectorTask> getResults() {
			return tasks;
		}

		public EventSupervisor getEventSupervisor() {
			return eventSupervisor;
		}

		public double getAverage() {
			return average;
		}
	}

	private BypassCountTester tester;

	@Override
	public void test(int insertersNum, int removersNum, int peakLength,
			int timeBetweenPeaks, int timeToRun, TaskContainer container,
			int extraInt) throws InterruptedException, InstantiationException,
			IllegalAccessException, ClassNotFoundException {
		NumberTakingInserter inserters[] = new NumberTakingInserter[insertersNum];
		for (int i = 0; i < inserters.length; i++)
			inserters[i] = new NumberTakingInserter(i, container, peakLength,
					timeBetweenPeaks, new ClockVectorTaskFactory(i,
							insertersNum, removersNum), extraInt);
		NumberTakingRemover removers[] = new NumberTakingRemover[removersNum];
		for (int i = 0; i < removers.length; i++)
			removers[i] = new NumberTakingRemover(i, container, extraInt,
					removersNum);
		tester = new BypassCountTester(inserters, removers, timeToRun,
				container);
		tester.runTest();
	}

	@Override
	public void test(int insertersNum, int removersNum, double lambda,
			int timeToRun, TaskContainer container, int extraInt)
			throws InterruptedException, InstantiationException,
			IllegalAccessException, ClassNotFoundException {
		NumberTakingInserter inserters[] = new NumberTakingInserter[insertersNum];
		for (int i = 0; i < inserters.length; i++)
			inserters[i] = new NumberTakingInserter(i, container, lambda,
					new ClockVectorTaskFactory(i, insertersNum, removersNum),
					extraInt);
		NumberTakingRemover removers[] = new NumberTakingRemover[removersNum];
		for (int i = 0; i < removers.length; i++)
			removers[i] = new NumberTakingRemover(i, container, extraInt,
					removersNum);
		tester = new BypassCountTester(inserters, removers, timeToRun,
				container);
		tester.runTest();
	}

	public List<ClockVectorTask> getResults() {
		return tester.getResults();
	}

	@Override
	public void accept(TesterVisitor v) {
		v.visit(this);
	}

	public EventSupervisor getEventSupervisor() {
		return tester.getEventSupervisor();
	}

	public double getAverage() {
		return tester.getAverage();
	}
}
